# app.py
"""
Flask应用工厂和主入口模块

该模块负责创建和配置Flask应用，包括：
- 数据库初始化
- 蓝图注册
- 定时任务配置
- 系统配置管理
"""

import logging
import os
import secrets
import string
from datetime import datetime
from logging.handlers import RotatingFileHandler
from typing import Optional, Dict, Any
import sys
from pathlib import Path


# 第三方库
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from flask import Flask, render_template, g, jsonify, current_app
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from sqlalchemy.exc import SQLAlchemyError

# 本地模块
from admin.admin import admin_bp
from admin.admin.admin_backup import BackupManager
from admin.jira.approval_route import approval_bp
from admin.jira.jira_route import jira_bp
from admin.wxwork.wxwork_route import wx_bp
from admin.wxwork.wxwork_sync import WeComSync
from admin.zktime.zk_route import zktime_bp
from admin.asset.asset_route import asset_bp

from inc import config
from inc.config_manager import SystemConfigManager
from inc.models import db, WechatConfig, DatabaseConfig, AdminUser, SystemConfig
from member import member_bp


# =============================================================================
# 工具函数
# =============================================================================

def format_datetime(value: Optional[datetime], format_str: str = '%Y-%m-%d %H:%M:%S') -> str:
    """
    格式化日期时间为字符串

    Args:
        value: 日期时间对象
        format_str: 格式字符串

    Returns:
        str: 格式化后的日期时间字符串
    """
    if value is None:
        return ""
    return value.strftime(format_str)


def format_date(value: Optional[datetime], format_str: str = '%Y-%m-%d') -> str:
    """
    格式化日期为字符串

    Args:
        value: 日期对象
        format_str: 格式字符串

    Returns:
        str: 格式化后的日期字符串
    """
    if value is None:
        return ""
    return value.strftime(format_str)


def _generate_secure_password(length: int = 12) -> str:
    """
    生成安全的随机密码

    Args:
        length: 密码长度

    Returns:
        str: 生成的随机密码
    """
    chars = string.ascii_letters + string.digits + '!@#$%'
    return ''.join(secrets.choice(chars) for _ in range(length))


# =============================================================================
# 数据库初始化函数
# =============================================================================

def _create_default_system_config(app: Flask) -> None:
    """创建默认系统配置"""
    try:
        default_config = SystemConfig()
        db.session.add(default_config)
        db.session.commit()
        app.logger.info("默认系统配置已创建")
    except SQLAlchemyError as e:
        db.session.rollback()
        app.logger.error(f"创建默认系统配置失败: {str(e)}")
        raise


def _create_default_admin_user(app: Flask) -> None:
    """创建默认管理员账户"""
    try:
        # 从环境变量获取密码或生成随机密码
        default_password = os.environ.get('DEFAULT_ADMIN_PASSWORD')
        if not default_password:
            default_password = _generate_secure_password()
            app.logger.warning(f"生成的默认管理员密码: {default_password}")

        default_admin = AdminUser(
            username='admin',
            real_name='系统管理员',
            is_super=True,
            create_time=datetime.now()
        )
        default_admin.password = default_password
        db.session.add(default_admin)
        db.session.commit()
        app.logger.info("默认管理员账户已创建")
    except SQLAlchemyError as e:
        db.session.rollback()
        app.logger.error(f"创建默认管理员失败: {str(e)}")
        raise


def _create_default_wechat_config(app: Flask) -> None:
    """创建默认企业微信配置"""
    try:
        default_config = WechatConfig(
            corp_id=app.config.get('WECHAT_CORP_ID', ''),
            corp_secret=app.config.get('WECHAT_CORP_SECRET', ''),
            agent_id=app.config.get('WECHAT_AGENT_ID', '')
        )
        db.session.add(default_config)
        db.session.commit()
        app.logger.info("默认企业微信配置已创建")
    except SQLAlchemyError as e:
        db.session.rollback()
        app.logger.error(f"创建默认企业微信配置失败: {str(e)}")
        raise


def _create_default_database_config(app: Flask) -> None:
    """创建默认数据库配置"""
    try:
        db_uri = app.config.get('SQLALCHEMY_DATABASE_URI', '')
        db_config = None

        if db_uri.startswith('sqlite:///'):
            db_path = db_uri.replace('sqlite:///', '')
            # 转换为绝对路径
            if not os.path.isabs(db_path):
                db_path = os.path.abspath(os.path.join(app.root_path, '..', db_path))

            db_config = DatabaseConfig(
                db_type='sqlite',
                database=db_path,
                is_active=True
            )
        elif db_uri.startswith('mysql+pymysql://'):
            parts = db_uri.replace('mysql+pymysql://', '').split('@')
            user_pass = parts[0].split(':')
            host_port_db = parts[1].split('/')
            host_port = host_port_db[0].split(':')

            db_config = DatabaseConfig(
                db_type='mysql',
                host=host_port[0],
                port=int(host_port[1]) if len(host_port) > 1 else 3306,
                username=user_pass[0],
                password=user_pass[1] if len(user_pass) > 1 else '',
                database=host_port_db[1],
                is_active=True
            )

        if db_config is not None:
            db.session.add(db_config)
            db.session.commit()
            app.logger.info("默认数据库配置已创建")
        else:
            app.logger.warning("未识别的数据库URI，未创建默认数据库配置")
    except Exception as e:
        db.session.rollback()
        app.logger.error(f"创建默认数据库配置失败: {str(e)}")
        raise


def _repair_database_path(app: Flask) -> None:
    """修复数据库文件路径"""
    try:
        db_uri = app.config.get('SQLALCHEMY_DATABASE_URI', '')
        if db_uri.startswith('sqlite:///'):
            db_path = db_uri.replace('sqlite:///', '')

            # 如果是相对路径，转换为绝对路径
            if not os.path.isabs(db_path):
                if getattr(sys, 'frozen', False):
                    # 打包环境
                    base_dir = os.path.dirname(sys.executable)
                else:
                    # 开发环境
                    base_dir = os.path.abspath(os.path.dirname(__file__))

                db_path = os.path.join(base_dir, db_path)

            # 确保目录存在
            db_dir = os.path.dirname(db_path)
            if db_dir and not os.path.exists(db_dir):
                os.makedirs(db_dir, exist_ok=True)

            # 更新配置
            app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
            app.logger.info(f"修复后的数据库路径: {db_path}")

    except Exception as e:
        app.logger.error(f"修复数据库路径失败: {str(e)}")

def _initialize_database(app: Flask) -> None:
    """初始化数据库并创建默认配置"""
    try:
        # 确保数据库目录存在
        db_uri = app.config.get('SQLALCHEMY_DATABASE_URI', '')
        if db_uri.startswith('sqlite:///'):
            db_path = db_uri.replace('sqlite:///', '')
            db_dir = os.path.dirname(db_path)
            if db_dir and not os.path.exists(db_dir):
                os.makedirs(db_dir, exist_ok=True)
                app.logger.info(f"创建数据库目录: {db_dir}")

        # 创建所有表
        db.create_all()
        app.logger.info("数据库表结构初始化完成")

        # 创建默认配置（如果不存在）
        if not WechatConfig.query.first():
            _create_default_wechat_config(app)

        if not DatabaseConfig.query.first():
            _create_default_database_config(app)

        if not AdminUser.query.first():
            _create_default_admin_user(app)

        if not SystemConfig.query.first():
            _create_default_system_config(app)

        app.logger.info("数据库默认配置初始化完成")
    except SQLAlchemyError as e:
        app.logger.error(f"数据库初始化失败: {str(e)}")
        # 尝试重新创建数据库文件
        if "unable to open database file" in str(e):
            app.logger.info("尝试修复数据库文件路径...")
            _repair_database_path(app)
            # 重试初始化
            db.create_all()
        else:
            raise

def _load_config_from_database(app: Flask) -> None:
    """从数据库加载配置到应用"""
    try:
        # 加载企业微信配置
        wechat_config = WechatConfig.query.first()
        if wechat_config:
            app.config.update(
                WECHAT_CORP_ID=wechat_config.corp_id,
                WECHAT_CORP_SECRET=wechat_config.corp_secret,
                WECHAT_AGENT_ID=wechat_config.agent_id
            )
            app.logger.info("企业微信配置已从数据库加载")

        # 加载系统配置
        system_config = SystemConfigManager.get_config()
        if system_config:
            app.config.update(
                SITE_NAME=system_config.site_name,
                SITE_TITLE=system_config.site_title,
                COMPANY_NAME=system_config.company_name,
                CONTACT_EMAIL=system_config.contact_email,
                FOOTER_INFO=system_config.footer_info
            )
            app.logger.info("系统配置已从数据库加载")

    except Exception as e:
        app.logger.error(f"数据库配置加载失败: {str(e)}")
        raise


# =============================================================================
# 应用工厂函数
# =============================================================================

def _register_blueprints(app: Flask) -> None:
    """注册所有蓝图"""
    blueprints = [
        (wx_bp, '/wxwork'),
        (jira_bp, '/jira'),
        (approval_bp, '/approval'),
        (admin_bp, '/admin'),
        (member_bp, '/member'),
        (zktime_bp, '/zktime'),
        (asset_bp, '/asset')
    ]

    for blueprint, url_prefix in blueprints:
        app.register_blueprint(blueprint, url_prefix=url_prefix)
        app.logger.debug(f"注册蓝图: {blueprint.name} -> {url_prefix}")


def _register_context_processors(app: Flask) -> None:
    """注册上下文处理器"""

    @app.context_processor
    def inject_system_config() -> Dict[str, Any]:
        """
        安全的系统配置注入器

        Returns:
            dict: 包含系统配置的字典
        """
        try:
            # 使用缓存避免重复查询
            if hasattr(g, 'system_config_dict'):
                return {'system_config': g.system_config_dict}

            system_config = SystemConfig.query.first()
            if system_config:
                config_dict = {
                    'site_name': system_config.site_name,
                    'site_title': system_config.site_title,
                    'company_name': system_config.company_name,
                    'contact_email': system_config.contact_email,
                    'footer_info': system_config.footer_info,
                }
                g.system_config_dict = config_dict
                return {'system_config': config_dict}
            else:
                default_config = {
                    'site_name': '项目管理系统',
                    'site_title': '项目管理',
                    'company_name': '公司名称',
                    'contact_email': 'contact@example.com',
                    'footer_info': '版权所有'
                }
                g.system_config_dict = default_config
                return {'system_config': default_config}

        except Exception as e:
            current_app.logger.error(f"系统配置上下文处理器错误: {str(e)}")
            default_config = {
                'site_name': '项目管理系统',
                'site_title': '项目管理',
                'company_name': '公司名称',
                'contact_email': 'contact@example.com',
                'footer_info': '版权所有'
            }
            return {'system_config': default_config}


def _register_error_handlers(app: Flask) -> None:
    """注册错误处理器"""

    @app.errorhandler(404)
    def page_not_found(e):
        """404错误页面处理器"""
        return render_template('404.html'), 404

    @app.errorhandler(500)
    def internal_server_error(e):
        """500错误页面处理器"""
        current_app.logger.error(f"服务器内部错误: {str(e)}")
        return render_template('500.html'), 500

    @app.errorhandler(403)
    def forbidden(e):
        """403错误页面处理器"""
        return render_template('403.html'), 403


def _register_request_hooks(app: Flask) -> None:
    """注册请求钩子"""

    @app.before_request
    def before_request():
        """确保系统配置在每次请求前可用"""
        try:
            SystemConfigManager.get_config()
        except Exception as e:
            current_app.logger.warning(f"系统配置加载失败: {str(e)}")


def create_app(config_name: str = 'default') -> Flask:
    """
    工厂函数创建Flask应用

    Args:
        config_name: 配置名称，默认为 'default'

    Returns:
        Flask: 创建的Flask应用实例
    """
    app = Flask(__name__)

    # 加载配置
    app.config.from_object(config.config[config_name])

    # 设置密钥
    app.secret_key = os.environ.get('SECRET_KEY')

    # 初始化扩展
    db.init_app(app)
    csrf = CSRFProtect(app)

    # CSRF豁免（确保这些确实不需要CSRF保护）
    csrf.exempt(admin_bp)
    csrf.exempt(zktime_bp)
    csrf.exempt(jira_bp)
    csrf.exempt(asset_bp)

    # 初始化登录管理
    login_manager = LoginManager()
    login_manager.init_app(app)
    login_manager.login_view = 'admin.admin_auth.login'
    login_manager.login_message = '请登录以访问此页面'

    @login_manager.user_loader
    def load_user(user_id):
        """用户加载回调"""
        return db.session.get(AdminUser, int(user_id))

    # 注册自定义过滤器
    app.jinja_env.filters['format_datetime'] = format_datetime
    app.jinja_env.filters['format_date'] = format_date

    # 注册蓝图
    _register_blueprints(app)

    # 注册上下文处理器和错误处理
    _register_context_processors(app)
    _register_error_handlers(app)

    # 注册请求钩子
    _register_request_hooks(app)

    # 禁用config中的数据库初始化（如果存在）
    app.config['SKIP_DB_CONFIG'] = True

    app.logger.info(f"Flask应用创建完成，使用配置: {config_name}")
    return app


# =============================================================================
# 定时任务相关函数
# =============================================================================

def _perform_scheduled_backup(app: Flask) -> None:
    """执行定时备份任务"""
    with app.app_context():
        try:
            system_config = SystemConfigManager.get_config()
            if system_config and system_config.backup_enabled:
                backup_manager = BackupManager()
                success, message = backup_manager.perform_backup(
                    operator_id=None,
                    backup_type="scheduled"
                )
                if success:
                    app.logger.info(f"定时备份成功: {message}")
                else:
                    app.logger.error(f"定时备份失败: {message}")
        except Exception as e:
            app.logger.error(f"定时备份执行异常: {str(e)}")


def _auto_sync_task(app: Flask) -> None:
    """企业微信自动同步任务"""
    try:
        with app.app_context():
            # 检查是否启用了自动同步
            wechat_config = WechatConfig.query.first()
            if wechat_config and wechat_config.auto_sync:
                sync = WeComSync()
                sync.sync_departments_and_users()
                app.logger.info("企业微信自动同步任务执行成功")
    except Exception as e:
        app.logger.error(f"企业微信同步任务失败: {str(e)}")


def _check_system_config(app: Flask) -> None:
    """系统配置检查任务"""
    try:
        with app.app_context():
            # 确保系统配置存在
            if not SystemConfigManager.get_config():
                _create_default_system_config(app)
                app.logger.info("系统配置检查：创建了默认配置")
    except Exception as e:
        app.logger.error(f"系统配置检查失败: {str(e)}")


def _setup_backup_job(app: Flask, scheduler: BackgroundScheduler) -> None:
    """配置备份任务"""
    try:
        system_config = SystemConfigManager.get_config()
        if system_config and system_config.backup_enabled and system_config.backup_schedule:
            cron_parts = system_config.backup_schedule.split()
            if len(cron_parts) == 5:
                scheduler.add_job(
                    func=lambda: _perform_scheduled_backup(app),
                    trigger=CronTrigger(
                        minute=cron_parts[0],
                        hour=cron_parts[1],
                        day=cron_parts[2],
                        month=cron_parts[3],
                        day_of_week=cron_parts[4]
                    ),
                    id='auto_backup',
                    replace_existing=True
                )
                app.logger.info(f"已设置定时备份任务: {system_config.backup_schedule}")
            else:
                app.logger.warning(f"备份计划格式错误: {system_config.backup_schedule}")
    except Exception as e:
        app.logger.error(f"配置备份计划失败: {str(e)}")


def _setup_sync_job(app: Flask, scheduler: BackgroundScheduler) -> None:
    """配置同步任务"""
    try:
        wechat_config = WechatConfig.query.first()
        if wechat_config and wechat_config.auto_sync:
            sync_interval = wechat_config.sync_interval or 1
            scheduler.add_job(
                func=lambda: _auto_sync_task(app),
                trigger='interval',
                hours=sync_interval,
                id='auto_sync',
                replace_existing=True
            )
            app.logger.info(f"已设置企业微信自动同步任务，间隔: {sync_interval}小时")
        else:
            app.logger.info("企业微信自动同步未启用")
    except Exception as e:
        app.logger.error(f"配置同步任务失败: {str(e)}")


def _setup_scheduler(app: Flask) -> None:
    """定时任务配置"""
    try:
        scheduler = BackgroundScheduler()

        # 配置备份任务
        _setup_backup_job(app, scheduler)

        # 配置同步任务
        _setup_sync_job(app, scheduler)

        # 配置系统检查任务
        scheduler.add_job(
            func=lambda: _check_system_config(app),
            trigger='interval',
            hours=24,
            id='config_check',
            replace_existing=True
        )

        # 添加应用关闭时的清理
        @app.teardown_appcontext
        def shutdown_scheduler(exception=None):
            """应用关闭时关闭调度器"""
            if scheduler.running:
                scheduler.shutdown()
                app.logger.info("定时任务调度器已关闭")

        scheduler.start()
        app.logger.info("定时任务调度器已启动")

    except Exception as e:
        app.logger.error(f"定时任务配置失败: {str(e)}")
        raise


# =============================================================================
# 日志配置
# =============================================================================

def _setup_logging(app: Flask) -> None:
    """配置应用日志"""
    # 确保日志目录存在
    logs_dir = os.path.join(app.root_path, 'logs')
    if not os.path.exists(logs_dir):
        os.makedirs(logs_dir, exist_ok=True)

    # 设置日志级别
    log_level = logging.INFO
    if app.config.get('DEBUG'):
        log_level = logging.DEBUG

    # 清除任何现有的处理器
    for handler in logging.root.handlers[:]:
        logging.root.removeHandler(handler)

    # 配置根日志记录器
    logging.basicConfig(
        level=log_level,
        format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
        handlers=[
            RotatingFileHandler(
                os.path.join(logs_dir, 'app.log'),
                maxBytes=1024 * 1024 * 10,  # 10MB
                backupCount=10,
                encoding='utf-8'
            ),
            logging.StreamHandler()
        ]
    )

    # 设置Flask应用日志记录器
    app.logger.setLevel(log_level)

    app.logger.info("日志系统配置完成")


# =============================================================================
# 主入口函数
# =============================================================================

def _load_config_from_exe_dir(app: Flask) -> None:
    """优先从exe同目录加载配置文件"""
    try:
        # 判断是否为打包环境
        if getattr(sys, 'frozen', False):
            # 打包环境：exe文件所在目录
            base_dir = Path(sys.executable).parent
        else:
            # 开发环境：脚本所在目录
            base_dir = Path(__file__).parent

        # 优先检查exe同目录的.env文件
        exe_dir_env = base_dir / '.env'
        temp_dir_env = Path(app.root_path) / '.env'

        if exe_dir_env.exists():
            # 从exe同目录加载
            from dotenv import load_dotenv
            load_dotenv(exe_dir_env)
            app.logger.info(f"从外部配置文件加载: {exe_dir_env}")
        elif temp_dir_env.exists():
            # 回退到临时目录
            from dotenv import load_dotenv
            load_dotenv(temp_dir_env)
            app.logger.info(f"从内部配置文件加载: {temp_dir_env}")
        else:
            app.logger.warning("未找到.env配置文件")

    except Exception as e:
        app.logger.error(f"配置文件加载失败: {str(e)}")

def main():
    """
    主入口函数

    负责：
    - 创建Flask应用
    - 初始化数据库和配置
    - 启动开发服务器
    """


    # 根据环境变量选择配置
    config_name = os.environ.get('FLASK_CONFIG', 'default')
    app = create_app(config_name)

    _load_config_from_exe_dir(app)

    # 基础路由
    @app.route('/')
    def index():
        """首页"""
        return render_template('index.html')

    @app.route('/health')
    def health_check():
        """健康检查端点"""
        return jsonify({
            'status': 'healthy',
            'timestamp': datetime.now().isoformat(),
            'service': 'project_management_system'
        })

    @app.route('/.well-known/appspecific/com.chrome.devtools.json')
    def handle_chrome_devtools():
        """处理Chrome开发者工具的自动请求"""
        return jsonify({}), 200

    # 初始化应用
    try:
        _setup_logging(app)

        with app.app_context():
            # 先修复可能的路径问题
            _repair_database_path(app)

            # 初始化数据库
            _initialize_database(app)

            # 加载数据库配置
            _load_config_from_database(app)

            # 设置定时任务（在生产环境中）
            if os.environ.get('FLASK_ENV') != 'development':
                _setup_scheduler(app)

        app.logger.info("应用初始化完成")

    except Exception as e:
        app.logger.error(f"应用初始化失败: {e}")

        # 在生产环境中，初始化失败应该退出
        if os.environ.get('FLASK_ENV') == 'production':
            raise

    # 检查是否使用GUI模式
    use_gui = os.environ.get('USE_GUI', 'false').lower() == 'true'

    if use_gui:
        # 使用GUI模式运行
        try:
            from gui_manager import run_with_gui
            app.logger.info("启动GUI模式...")
            run_with_gui(app)
        except ImportError as e:
            app.logger.warning(f"GUI模式不可用: {e}")
            app.logger.info("回退到命令行模式")
            _run_command_line(app)
        except Exception as e:
            app.logger.error(f"GUI模式启动失败: {e}")
            _run_command_line(app)
    else:
        # 使用命令行模式运行
        _run_command_line(app)




def _run_command_line(app):
    """命令行模式运行应用"""
    debug_mode = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true'
    host = os.environ.get('FLASK_HOST', '0.0.0.0')
    port = int(os.environ.get('FLASK_PORT', '5000'))

    app.logger.info(f"启动服务在 {host}:{port} (debug={debug_mode})")
    app.run(host=host, port=port, debug=debug_mode)



if __name__ == '__main__':
    main()